﻿/**
 * @file XWidgetP2J.cpp
 * Copyright (c) Gaaagaa. All rights reserved.
 * 
 * @author  : Gaaagaa
 * @date    : 2020-12-20
 * @version : 1.0.0.0
 * @brief   : 实现 PDF转图片 面板部件。
 */

#include "XWidgetP2J.h"
#include "ui_XWidgetP2J.h"

#include <QMessageBox>
#include <QFileDialog>
#include <QDir>

////////////////////////////////////////////////////////////////////////////////
// XWidgetP2J

//====================================================================

// 
// XWidgetP2J : constructor/destructor
// 

XWidgetP2J::XWidgetP2J(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::XWidgetP2J)
    , m_xbt_contine(X_FALSE)
{
    ui->setupUi(this);

    //======================================

    QRegExpValidator * xRegDigitValidator =
            new QRegExpValidator(QRegExp("^[1-9][0-9]+$"), this);

    ui->lineEdit_PageNoBegin->setValidator(xRegDigitValidator);
    ui->lineEdit_PageNoEnd->setValidator(xRegDigitValidator);
    ui->lineEdit_JpegMaxW->setValidator(xRegDigitValidator);
    ui->lineEdit_JpegMaxH->setValidator(xRegDigitValidator);

    ui->comboBox_PageRange->addItem(tr("全部"));
    ui->comboBox_PageRange->addItem(tr("奇数页"));
    ui->comboBox_PageRange->addItem(tr("偶数页"));
    ui->comboBox_PageRange->addItem(tr("自定义"));
    ui->comboBox_PageRange->setCurrentIndex(0);

    ui->label_PageNo->hide();
    ui->lineEdit_PageNoBegin->hide();
    ui->label_PageNoTo->hide();
    ui->lineEdit_PageNoEnd->hide();

    enableCtrl(true);

    //======================================

    connect(this, SIGNAL(reportInfo(int, int, bool, const QString &)),
            this, SLOT(on_reportInfo(int, int, bool, const QString &)),
            Qt::ConnectionType::QueuedConnection);

    connect(this, SIGNAL(workFinished()),
            this, SLOT(on_workFinished()),
            Qt::ConnectionType::QueuedConnection);

    //======================================
}

XWidgetP2J::~XWidgetP2J(void)
{
    delete ui;

    m_xbt_contine = X_FALSE;
    if (m_xtrd_worker.joinable())
    {
        m_xtrd_worker.join();
    }

    m_xpdf_reader.close();
}

//====================================================================

//
// XWidgetP2J : inner invoking
//

void XWidgetP2J::enableCtrl(bool bEnable)
{
    ui->lineEdit_PDFFile->setEnabled(bEnable);
    ui->btn_PDFFile->setEnabled(bEnable);
    ui->btn_PDFFileReset->setEnabled(bEnable && m_xpdf_reader.is_open());

    ui->comboBox_PageRange->setEnabled(bEnable);
    ui->lineEdit_PageNoBegin->setEnabled(bEnable);
    ui->lineEdit_PageNoEnd->setEnabled(bEnable);

    ui->dbSpinBox_JpegZoom->setEnabled(bEnable);
    ui->checkBox_JpegMaxSize->setEnabled(bEnable);
    ui->lineEdit_JpegMaxW->setEnabled(bEnable && ui->checkBox_JpegMaxSize->isChecked());
    ui->lineEdit_JpegMaxH->setEnabled(bEnable && ui->checkBox_JpegMaxSize->isChecked());

    ui->lineEdit_OutDir->setEnabled(bEnable);
    ui->btn_OutDir->setEnabled(bEnable);

    ui->btn_OutStart->setEnabled(bEnable);
    ui->btn_OutCancel->setEnabled(!bEnable);
}

bool XWidgetP2J::queryPageNoQueue(xvec_no_t & xvec_no)
{
    x_int32_t xit_page_count = m_xpdf_reader.get_page_count();
    if (xit_page_count <= 0)
    {
        QMessageBox::information(
            this,
            tr("提示"),
            tr("当前的 PDF 文件没有页面可提取！"));
        ui->lineEdit_PDFFile->setFocus();
        return false;
    }

    xvec_no.clear();

    QString strMethods = ui->comboBox_PageRange->currentText();
    if (tr("全部") == strMethods)
    {
        for (x_int32_t xit_no = 0; xit_no < xit_page_count; ++xit_no)
        {
            xvec_no.push_back(xit_no);
        }
    }
    else if (tr("奇数页") == strMethods)
    {
        for (x_int32_t xit_no = 0; xit_no < xit_page_count; xit_no += 2)
        {
            xvec_no.push_back(xit_no);
        }
    }
    else if (tr("偶数页") == strMethods)
    {
        for (x_int32_t xit_no = 1; xit_no < xit_page_count; xit_no += 2)
        {
            xvec_no.push_back(xit_no);
        }
    }
    else if (tr("自定义") == strMethods)
    {
        x_int32_t xit_no_bgn = ui->lineEdit_PageNoBegin->text().toInt();
        x_int32_t xit_no_end = ui->lineEdit_PageNoEnd->text().toInt();

        if ((xit_no_bgn <= 0) || (xit_no_bgn > xit_page_count))
        {
            QMessageBox::information(
                        this,
                        tr("提示"),
                        tr("起始页码 设置的范围不正确！"));
            ui->lineEdit_PageNoBegin->setFocus();
            return false;
        }

        if ((xit_no_end <= 0) || (xit_no_end > xit_page_count))
        {
            QMessageBox::information(
                this,
                tr("提示"),
                tr("结束页码 设置的范围不正确！"));
            ui->lineEdit_PageNoEnd->setFocus();
            return false;
        }

        if (xit_no_bgn > xit_no_end)
        {
            QMessageBox::information(
                this,
                tr("提示"),
                tr("起始页码 不能大于 结束页码！"));
            ui->lineEdit_PageNoBegin->setFocus();
            return false;
        }

        for (x_int32_t xit_no = xit_no_bgn - 1; xit_no < xit_no_end; ++xit_no)
        {
            xvec_no.push_back(xit_no);
        }
    }
    else
    {
        QMessageBox::information(
            this,
            tr("提示"),
            tr("未能识别的页码序列提取方式：") + strMethods);
        ui->comboBox_PageRange->setFocus();
        return false;
    }

    return true;
}

bool XWidgetP2J::queryOutDir(std::string & xstr_dir)
{
    QString strDir = ui->lineEdit_OutDir->text();
    if (strDir.isEmpty() || !QDir::isAbsolutePath(strDir))
    {
        QMessageBox::information(
            this,
            tr("提示"),
            QString(tr("输出目录只能使用绝对路径，请正确设置输出目录！")).arg(strDir));
        ui->lineEdit_OutDir->setFocus();
        return false;
    }
    QDir outDir(strDir);
    if (!outDir.exists())
    {
        if (!outDir.mkpath(strDir))
        {
            QMessageBox::information(
                this,
                tr("提示"),
                QString(tr("创建输出目录 %1 失败！操作取消。")).arg(strDir));
            ui->lineEdit_OutDir->setFocus();
            return false;
        }
    }
    else if (!outDir.isEmpty())
    {
        if (QMessageBox::No ==
                QMessageBox::question(
                    this,
                    tr("提示"),
                    tr("当前输出目录不为空，输出图片时，可能会覆盖原目录中的文件！\n"
                       "是否继续执行输出操作？\n"
                       "选择 “是”，则继续；选择 “否”，则取消操作。"),
                    QMessageBox::Yes | QMessageBox::No,
                    QMessageBox::No))
        {
            ui->lineEdit_OutDir->setFocus();
            return false;
        }
    }

    xstr_dir = strDir.toLocal8Bit().data();
    return true;
}

void XWidgetP2J::thread_work(
                    const xvec_no_t   & xvec_no,
                    x_lfloat_t          xlft_zoom,
                    x_uint32_t          xut_mcx,
                    x_uint32_t          xut_mcy,
                    const std::string & xstr_dir)
{
    x_char_t   xszt_path[TEXT_LEN_512] = { 0 };
    x_char_t * xszt_name = xszt_path + sprintf(xszt_path, "%s", xstr_dir.c_str());

    x_uint32_t xut_cx    = 0;
    x_uint32_t xut_cy    = 0;
    x_pvoid_t  xbits_ptr = X_NULL;
    x_int32_t  xit_pitch = 0;
    x_errno_t  xerr_no   = X_ERR_UNKNOW;
    x_int32_t  xit_err   = X_ERR_UNKNOW;

    int nCount = (int)xvec_no.size();
    int nIndex = 0;

    for (xvec_no_t::const_iterator
         citer = xvec_no.begin();
         (citer != xvec_no.end()) && m_xbt_contine;
         ++citer)
    {
        sprintf(const_cast< x_char_t * >(xszt_name), "%08d.jpg", *citer + 1);

        //======================================

        xut_cx    = xut_mcx;
        xut_cy    = xut_mcy;
        xbits_ptr = X_NULL;
        xit_pitch = 0;

        xerr_no = m_xpdf_reader.render_page(
                                    *citer,
                                    (x_float_t)xlft_zoom,
                                    xut_cx,
                                    xut_cy,
                                    xbits_ptr,
                                    xit_pitch);
        if (XERR_FAILED(xerr_no))
        {
            emit reportInfo(
                        nCount,
                        ++nIndex,
                        true,
                        QString(
                            "渲染 PDF 页面（页码：%1）时产生错误（%2），将终止页面提取流程！")
                            .arg(*citer + 1)
                            .arg(XPDF_reader_t::xerrno_name(xerr_no)));
            break;
        }

        //======================================

        m_jenc_handle.config_dst(JCTRL_MODE_FILE, (j_handle_t)xszt_path, 0);
        xit_err = m_jenc_handle.rgb_to_dst(
                                    (j_mem_t)xbits_ptr,
                                    xit_pitch,
                                    (j_int_t)xut_cx,
                                    (j_int_t)xut_cy,
                                    JCTRL_CS_BGRA);
        if (JENC_ERR_OK != xit_err)
        {
            emit reportInfo(
                        nCount,
                        ++nIndex,
                        true,
                        QString(
                            "输出图片（路径：%1）时产生错误（%2），将终止页面提取流程！")
                            .arg(QString::fromLocal8Bit(xszt_path))
                            .arg(jenc_errno_name(xit_err)));
            break;
        }

        emit reportInfo(
                    nCount,
                    ++nIndex,
                    false,
                    QString("输出图片：%1")
                    .arg(QString::fromLocal8Bit(xszt_path)));

        //======================================
    }

    emit workFinished();
}

//====================================================================

// 
// XWidgetP2J : slots
// 

void XWidgetP2J::on_reportInfo(int nCount, int nIndex, bool isError, const QString & strInfo)
{
    ui->progressBar_OutProgress->setRange(0, nCount);
    ui->progressBar_OutProgress->setValue(nIndex);
    ui->textEdit_Log->append(strInfo);

    if (isError)
    {
        QMessageBox::warning(this, tr("告警"), strInfo);
    }
}

void XWidgetP2J::on_workFinished()
{
    m_xbt_contine = X_FALSE;
    if (m_xtrd_worker.joinable())
    {
        m_xtrd_worker.join();
    }

    enableCtrl(true);
}

void XWidgetP2J::on_btn_PDFFile_clicked()
{
    //======================================

    QString strFile =
        QFileDialog::getOpenFileName(
            nullptr,
            tr("选择 PDF 文件"),
            tr(""),
            tr("PDF文件(*.pdf)"));
    if (strFile.isEmpty())
    {
        return;
    }

    //======================================

    std::string xstr = strFile.toLocal8Bit().data();

    m_xpdf_reader.close();

    x_errno_t xerr_no = m_xpdf_reader.open(xstr.c_str(), X_NULL);
    if (XERR_FAILED(xerr_no))
    {
        QMessageBox::warning(
            this,
            tr("文件错误"),
            QString::asprintf(
                "读取PDF文件[%s]信息失败，返回错误码：%s",
                xstr.c_str(),
                XPDF_reader_t::xerrno_name(xerr_no)));
        return;
    }

    //======================================

    ui->lineEdit_PDFFile->setText(strFile);
    ui->btn_PDFFileReset->setEnabled(true);

    ui->lineEdit_PageCount->setText(
                QString(tr("%1")).arg(m_xpdf_reader.get_page_count()));

    on_comboBox_PageRange_currentIndexChanged(
                ui->comboBox_PageRange->currentIndex());

    if (ui->lineEdit_OutDir->text().isEmpty())
    {
        QString strDir = strFile;
        if (0 == QString::compare(
                    strDir.right(4), tr(".pdf"), Qt::CaseInsensitive))
            strDir.replace(strDir.length() - 4, 4, '/');
        else
            strDir.append('/');
        ui->lineEdit_OutDir->setText(strDir);
    }

    x_lfloat_t xlft_pcx = 0.0;
    x_lfloat_t xlft_pcy = 0.0;
    if (XERR_SUCCEED(m_xpdf_reader.get_page_size(0, xlft_pcx, xlft_pcy)))
    {
        x_lfloat_t xlft_zoom = ui->dbSpinBox_JpegZoom->value();
        if (xlft_zoom < 1.0)
            xlft_zoom = 1.0;
        ui->lineEdit_JpegMaxW->setText(
            QString::asprintf("%d", (x_uint32_t)(xlft_zoom * xlft_pcx)));
        ui->lineEdit_JpegMaxH->setText(
            QString::asprintf("%d", (x_uint32_t)(xlft_zoom * xlft_pcy)));
    }
    else
    {
        ui->lineEdit_JpegMaxW->setText(tr(""));
        ui->lineEdit_JpegMaxH->setText(tr(""));
    }

    ui->progressBar_OutProgress->setValue(0);

    //======================================
}

void XWidgetP2J::on_btn_PDFFileReset_clicked()
{
    m_xpdf_reader.close();

    //======================================

    ui->lineEdit_PDFFile->setText(tr(""));
    ui->btn_PDFFileReset->setEnabled(false);

    ui->lineEdit_PageCount->setText(tr(""));

    ui->comboBox_PageRange->setCurrentIndex(0);
    on_comboBox_PageRange_currentIndexChanged(
                ui->comboBox_PageRange->currentIndex());

    ui->lineEdit_OutDir->setText(tr(""));
    ui->progressBar_OutProgress->setMaximum(100);
    ui->progressBar_OutProgress->setValue(0);
    ui->textEdit_Log->clear();

    //======================================
}

void XWidgetP2J::on_comboBox_PageRange_currentIndexChanged(int index)
{
    if ((tr("自定义") == ui->comboBox_PageRange->currentText()) &&
        m_xpdf_reader.is_open())
    {
        ui->label_PageNo->show();
        ui->lineEdit_PageNoBegin->show();
        ui->label_PageNoTo->show();
        ui->lineEdit_PageNoEnd->show();

        ui->lineEdit_PageNoBegin->setText(tr("1"));
        ui->lineEdit_PageNoEnd->setText(
                    QString(tr("%1")).arg(m_xpdf_reader.get_page_count()));
    }
    else
    {
        ui->label_PageNo->hide();
        ui->lineEdit_PageNoBegin->hide();
        ui->label_PageNoTo->hide();
        ui->lineEdit_PageNoEnd->hide();
    }
}

void XWidgetP2J::on_checkBox_JpegMaxSize_toggled(bool checked)
{
    ui->lineEdit_JpegMaxW->setEnabled(checked);
    ui->lineEdit_JpegMaxH->setEnabled(checked);
}

void XWidgetP2J::on_btn_OutDir_clicked()
{
    QString strDir =
        QFileDialog::getExistingDirectory(nullptr, tr("选择输出目录"));
    if (!strDir.isEmpty())
    {
        if ((strDir.right(1) != tr("/")) ||
            (strDir.right(1) != tr("\\")))
        {
            strDir.append('/');
        }
        ui->lineEdit_OutDir->setText(strDir);
    }
}

void XWidgetP2J::on_btn_OutStart_clicked()
{
    //======================================

    if (!m_xpdf_reader.is_open())
    {
        QMessageBox::information(
            this,
            tr("提示"),
            tr("尚未指定所要提取的 PDF 文件！"));
        ui->lineEdit_PDFFile->setFocus();
        return;
    }

    //======================================

    // 生成要提取页面的 页码号 序列
    xvec_no_t xvec_no;
    if (!queryPageNoQueue(xvec_no))
        return;
    if (xvec_no.empty())
    {
        QMessageBox::information(
            this,
            tr("提示"),
            tr("当前设置，并没有可提取的页面！"));
        ui->comboBox_PageRange->setFocus();
        return;
    }

    // 输出图片的缩放倍率
    x_lfloat_t xlft_zoom = ui->dbSpinBox_JpegZoom->value();
    if (xlft_zoom < 0.01)
    {
        QMessageBox::information(
            this,
            tr("提示"),
            tr("输出图片的缩放倍率过小，操作取消！"));
        ui->dbSpinBox_JpegZoom->setFocus();
        return;
    }

    // 输出图片限制的宽高
    x_uint32_t xut_mcx = 0;
    x_uint32_t xut_mcy = 0;
    if (ui->checkBox_JpegMaxSize->isChecked())
    {
        xut_mcx = ui->lineEdit_JpegMaxW->text().toUInt();
        xut_mcy = ui->lineEdit_JpegMaxH->text().toUInt();
    }

    // 输出目录
    std::string xstr_dir;
    if (!queryOutDir(xstr_dir))
    {
        return;
    }

    //======================================
    // 启动工作线程

    ui->progressBar_OutProgress->setValue(0);
    ui->textEdit_Log->clear();

    m_xbt_contine = X_FALSE;
    if (m_xtrd_worker.joinable())
    {
        m_xtrd_worker.join();
    }

    m_xbt_contine = X_TRUE;
    m_xtrd_worker =
        std::move(
            std::thread(
                [this](
                        const xvec_no_t   & xvec_no,
                        x_lfloat_t          xlft_zoom,
                        x_uint32_t          xut_mcx,
                        x_uint32_t          xut_mcy,
                        const std::string & xstr_dir
                    ) -> void
                {
                    this->thread_work(
                        xvec_no, xlft_zoom, xut_mcx, xut_mcy, xstr_dir);
                },
                std::move(xvec_no),
                xlft_zoom,
                xut_mcx,
                xut_mcy,
                std::move(xstr_dir)
            )
        );

    //======================================

    enableCtrl(false);

    //======================================
}

void XWidgetP2J::on_btn_OutCancel_clicked()
{
    m_xbt_contine = X_FALSE;
    if (m_xtrd_worker.joinable())
    {
        m_xtrd_worker.join();
    }

    enableCtrl(true);
}
